package mdstat;

use English;

$mdstat::last_error=undef;
$mdstat::last_rc=0;

#########################################################################
# The new method creates an object which contains all the details of	#
# all the raid devices on the local machine - not just those in a	#
# particular fsmap.							#
# Each device is a key in the RAID hash element, and this element	#
# is itself a hash consisting of several fields:			#
#									#
#	NAME		- Name of device  - /dev/md23			#
#	STATE		- active|inactive				#
#	PERSONALITY	- raid1|fs1					#
#	SYNC		- sync|unsync|syncing				#
#	SYNCEDBLOCKS	- sync'd blocks					#
#	TOTALBLOCKS	- total size of raid device			#
#	RATE		- Kb/sec					#
#	ESTCOMPLETION	- est completion (minutes)			#
#	VALID		- local|remote|both				#
#									#
# Note that working out which device is invalid is typically hard!	#
# We attempt to do this here by examing the 2 lines:			#
# md10 : active raid1 lvmc[1] ndc[0](F)					#
#        20480 blocks [2/1] [_U]					#
#									#
# The numbers in brackets in the first line are treated like ordering	#
# information, to be taken as defined in the second [_U] line. Thus	#
# these numbers are sorted, and the relevant characters taken from the	#
# [_U] setting. Hence in the above case ndc is _ and lvmc is U. Thus	#
# the remote copy is out of date!					#
# Later versions now look at the "dev" information, and use the		#
# major numbers to identify the device type, for example:		#
#									#
# md21 : active raid1 [dev 2b:6c][1] [dev fe:05][0]			#
#     155648 blocks [2/2] [UU]						#
#									#
# Also for 2.6 kernels we need to be aware that the dev-mapper		#
# devices will appear similar to the following:				#
#									#
# md10 : active raid1 dm-1[0] ndk[1]					#
#     20480 blocks [2/2] [UU]						#
#									#
#########################################################################

$mdstat::nbd_major=0x2b;
$mdstat::mapper_major=0xfe;

sub new {
my $proto=shift;
my $class=ref($proto) || $proto;
my $self={};
my %mdstat=();
my ($fd,@f,$count);
my ($this,$state,$personality,$sync);
my ($valid,$sblocks,$tblocks,$rate,$esttime);
my ($devs,$record,$check_next);
my ($dev1,$dev2,$num1,$num2,$state1,$state2,$has_fr1);

	#################################################################
	# We can only tell that fr1 is running by looking at the	#
	# list of loaded modules!!!					#
	#################################################################
	$has_fr1=0;
	if(open($fd,"/proc/modules")) {
		while(<$fd>) {
			chomp;
			if((split(/\s+/,$_))[0] eq "fr1") {
				$has_fr1=1;
			}
		}
		close($fd);
	}
	
	$count=0;
	if(!open($fd,"/proc/mdstat")) {
		$mdstat::last_error="Unable to open status file";
		$mdstat::last_rc=1;
		return undef;
	}
	while(<$fd>) {
		chomp;

	#################################################################
	# We get three lines of information for each device, so stop	#
	# when we see a device label and parse that information.	#
	# Line will be in ether of the following formats:		#
	# md1 : active raid1 lvmc[2] ndb[1]				#
	# md1 : active raid1 [dev fe:01][0] ndb[1]			#
	# md2 : active raid1 [dev 2b:70][1] [dev fe:03][0]		#
	# md1 : active raid1 dm-1[0] ndk[1]				#
	#################################################################

		if(/^md[0-9]+ :/) {
			@f=split(/\s+/);
			$this=$f[0];
			$state=$f[2];
			$personality=$f[3];
			if($has_fr1) {
				$personality="fr1";
			}
			#################################################
			# We deal with the different device mapper	#
			# layout first, then the lvm v1 layout.		#
			#################################################
			$check_next=0;
			$num1=$num2=undef;
			if($f[4] =~ /^\[dev/) {
				($dev1,$num1)=($f[5] =~ /([a-f0-9]+:[a-f0-9]+)\]\[([0-9]+)\]/);
				$check_next=6;
			}
			if($f[4] =~ /^lvm/) {
				($dev1,$num1)=($f[4] =~ /(.+)\[([0-9]+)\]/);
				$check_next=5;
			}
			if($f[4] =~ /^dm-/) {
				($dev1,$num1)=($f[4] =~ /(.+)\[([0-9]+)\]/);
				$check_next=5;
			}
			if($f[4] =~ /^nd/) {
				($dev1,$num1)=($f[4] =~ /(.+)\[([0-9]+)\]/);
				$check_next=5;
			}

			# Invalid format, so give error
			if($check_next==0 || !defined($dev1)) {
				print STDERR "Invalid RAID entry: $f[4]\n";
				close($fd);
				return undef;
			}

			if(!exists($f[$check_next])) {
				$dev2="??"; $num2="1";
			} else {
				if($f[$check_next] =~ /^\[dev/) {
					($dev2,$num2)=($f[$check_next+1] =~ /([a-f0-9]+:[a-f0-9]+)\]\[([0-9]+)\]/);
					$check_next=6;
				}
				if($f[$check_next] =~ /^lvm/) {
					($dev2,$num2)=($f[$check_next] =~ /(.+)\[([0-9]+)\]/);
					$check_next=5;
				}
				if($f[$check_next] =~ /^dm-/) {
					($dev2,$num2)=($f[$check_next] =~ /(.+)\[([0-9]+)\]/);
					$check_next=5;
				}
				if($f[$check_next] =~ /^nd/) {
					($dev2,$num2)=($f[$check_next] =~ /(.+)\[([0-9]+)\]/);
					$check_next=5;
				}
			}

			if(!defined($num1) || !defined($num2)) {
				close($fd);
				return undef;
			}
			if($num1<$num2) {
				$num1=1; $num2=2;
			} else {
				$num1=2; $num2=1;
			}

			$sync="";
			$sblocks=$tblocks="";
			$rate="";
			$esttime="";

		#########################################################
		# Read in the next line to understand status of array	#
		# in the format:					#
		# 53184 blocks [2/1] [_U]				#
		#########################################################
			$_=<$fd>; chomp;
			@f=split(/\s+/);
			($devs,$valid)=($f[3] =~ /\[([0-9]+)\/([0-9]+)\]/);
			if($devs == $valid) {
				$sync="sync";
				$valid="both";
			#################################################
			# In sync so consume the next line, which will	#
			# be blank.					#
			#################################################
				$_=<$fd>; chomp;
			} else {
				$state1=substr($f[4],$num1,1);
				$state2=substr($f[4],$num2,1);
				if($state1 eq "_") {
					if($dev1 =~ /^lvm/ || $dev1 =~ /^fe:/ || $dev1 =~ /^dm-/) {
						$valid="remote";
					} else {
						$valid="local";
					}
				}
				if($state2 eq "_") {
					if($dev2 =~ /^lvm/ || $dev2 =~ /^fe:/ || $dev2 =~ /^dm-/) {
						$valid="remote";
					} else {
						$valid="local";
					}
				}

				$_=<$fd>; chomp;
				if(/^\s*$/) {
					$sync="unsync";
				} else {
					#       [===================>.]  recovery = 95.0% (20416/20416) finish=0.0min speed=2268K/sec
					@f=split(/\s+/);
					$sync="syncing";
					($sblocks,$tblocks)=($f[5] =~ /\(([0-9]+)\/([0-9]+)\)/);
					($rate)=($f[7] =~ /speed=([0-9]+)K/);
					($esttime)=($f[6] =~ /=([0-9]+\.[0-9]+)/);
				}
			}

		#########################################################
		# Now store the information in the mdstat hash		#
		#########################################################

			$record={
				NAME		=> $this,
				STATE		=> $state,
				PERSONALITY	=> $personality,
				SYNC		=> $sync,
				SYNCEDBLOCKS	=> $sblocks,
				TOTALBLOCKS	=> $tblocks,
				RATE		=> $rate,
				ESTCOMPLETION	=> $esttime,
				VALID		=> $valid
			};

			$count++;

			$mdstat{$this}=$record;
		}
	}

	$self->{COUNT}=$count;
	$self->{RAID}=\%mdstat;
	$mdstat::last_error="OK";
	$mdstat::last_rc=0;
	bless($self,$class);
	return $self;
}

#########################################################################
# The get method has an argumennt DEV => md22, and it will return	#
# undef, or a reference to a hash consisting of all the fields		#
# mentioned at the top of this field for the particular device.		#
#########################################################################

sub get {
my $self=shift;
my %args=@_;
my $dev;

	if(!exists($args{DEV})) {
		$mdstat::last_error="Missing function arguments";
		$mdstat::last_rc=1;
		return undef;
	}

	#################################################################
	# Remove all characters apart from the numbers, allowing	#
	# the device to be specified as /dev/md22, 22, or md22.		#
	#################################################################

	$dev=$args{DEV};
	$dev =~ s/\///g;
	$dev =~ s/[a-z]+//g;
	$dev="md$dev";

	if(!exists($self->{RAID}->{$dev})) {
		$mdstat::last_error="Unknown Raid device $dev";
		$mdstat::last_rc=1;
		return undef;
	}

	#################################################################
	# Ok the specified device exist, so return a reference to it	#
	# which should be treated as read-only!!			#
	#################################################################

	$mdstat::last_error="OK";
	$mdstat::last_rc=0;
	return $self->{RAID}->{$dev};
}

#########################################################################
# We return a count of how many devices need synchronisation (which	#
# includes any devices that are part way through synchronisation.	#
#########################################################################

sub need_sync_count {
my $self=shift;
my $count=0;

	foreach (keys(%{$self->{RAID}})) {
		print "Checking:: $_\n";
		if($self->{RAID}->{$_}->{VALID} ne "both") {
			$count++;
		}
	}
	$mdstat::last_error="OK";
	$mdstat::last_rc=0;
	return $count;
}

sub insync {
my $self=shift;
my $count=0;

	foreach (keys(%{$self->{RAID}})) {
		if($self->{RAID}->{$_}->{VALID} eq "both") {
			$count++;
		}
	}
	$mdstat::last_error="OK";
	$mdstat::last_rc=0;
	return $count;
}

1;

__END__

=head1 NAME

mdstat.pm - Object interface to RAID device status information

=head1 SYNOPSYS

    # Create a new object
    $obj=new mdstat;

    # Return details of a specified raid device
    $hashref=$obj->get(DEV => "md22");

    # Return the number of devices that need synchronisation
    $needsync=$obj->need_sync_count;

    # Return the number of devices that are currently synchronised
    $insync=$obj->insync;

=head1 GLOBAL VARIABLES

This module makes use of the following global variables to pass information
back to the caller - information that is available even if the object
creation failed.

=item B<mdstat::last_error>

This contains a text string containing the status of the last call to this
module, such as "OK", or some error text.

=item B<mdstat::last_rc>

A numeric error code from the last call to this module, typically 0 if no
problems were encountered.

=head1 DESCRIPTION

This module presents an interface to the contents of the /proc/mdstat
file. It allows easier access to understanding the particular RAID 
configuration used for all devices in the cluster.

Please notice that it contains details of all RAID devices, not just those
used for a particular application. However, use of the B<fsmap> object
can help perform that function by returning the list of RAID devices
for an application, then use of the B<get> method provided by this module for
each device number.

=head1 Supported Methods

=over 4

=item B<new>

This will parse the contents of the /proc/mdstat file and store the
details in the object returned. If an error is encountered the global
variables mentioned above will be set, and the returned value will be
B<undef>.

=item B<get(DEV => "md22")>

This will return a reference to details for the specified RAID device.
All characters apart from numbers are removed, allowing the device name to
be specified as above, or "/dev/md22", or just "22".

If the specified RAID device exists in the object, (i.e. existed when the
object was created), then a reference to a hash containing details of the
object will be returned. If the specified RAID device did not exist, then it
will return the B<undef> value.

If a hash is returned then it will consist of the following keys:

=over 4

=item B<NAME>

The full name of the RAID device in question, such as "/dev/md22".

=item B<STATE>

Whether the specified RAID device is currently in use - it will return the
string value "active" or "inactive". An "inactive" device indicates that the 
RAID device has been defined, but is not currently in use - this typically
happens when the specified application is stopping or starting.

=item B<SYNC>

This will return the value to indicate whether the local and remote mirrors
are synchronised or not. The value returned will be "sync" when everything is
OK, "unsync" when they are not synchronised, and "syncing" when 
synchronisation of this device is currently taking place.

=item B<SYNCEDBLOCKS>

The number of blocks (1Kb in size) that are currently in sync for the
specified device. This field is only present when the "SYNC" value is
returned as "syncing".

=item B<TOTALBLOCKS>

The total number of blocks that make up the RAID device - i.e. the size
of the device.

=item B<RATE>

This is only presenting when the "SYNC" value is returned as "syncing". This
indicates the current Kb/sec rate of synchronisation of the device.

=item B<ESTCOMPLETION>

The number of minutes, (including a fraction), that the completion of the
synchronisation will occur. This obviously changes as the load on the network
and or either/both of the servers change.

=item B<VALID>

A quick indication of which mirror is valid - it will return the string
value "both" when there are no problems, "local" when the remote copy is
invalid and "remote" when the local copy is in error.

=back

=item B<need_sync_count>

This will return the number of devices that are not currently synchronised.
It will also include in this count any devices that are partially synchronised
at present.

=item B<insync>

This returns the number of RAID devices that are currently completely
synchronised at present.

